package org.xx.armory.service;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.Marker;
import org.slf4j.MarkerFactory;
import org.xx.armory.bindings.BoundParam;
import org.xx.armory.bindings.BoundParamCollection;
import org.xx.armory.commons.AbstractLifeCycle;
import org.xx.armory.commons.LifeCycle;
import org.xx.armory.security.AuthenticationToken;

import java.lang.reflect.Array;
import java.math.BigDecimal;
import java.util.Date;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.function.Supplier;

import static org.apache.commons.lang3.StringUtils.trimToEmpty;
import static org.apache.commons.lang3.time.DateFormatUtils.format;
import static org.apache.commons.lang3.time.DateFormatUtils.formatUTC;
import static org.apache.commons.text.StringEscapeUtils.escapeJson;
import static org.xx.armory.commons.Validators.notBlank;
import static org.xx.armory.commons.Validators.notNull;

/**
 * 服务上下文。
 * <p>此类包含了运行服务时需要的上下文数据。</p>
 *
 * @author Haart
 */
public final class ServiceContext
        extends AbstractLifeCycle
        implements LifeCycle {
    public static final String TOKEN_ID_KEY = "__token_id__";
    public static final String ROUTE_KEY = "__route__";
    public static final String PARAMS_KEY = "__params__";

    private static final Marker TASK_LOGGER_MARKER = MarkerFactory.getMarker("Task");
    private static final String LOG_DETAIL_SEPARATOR = "\n\n";

    private final Logger logger = LoggerFactory.getLogger(ServiceContext.class);
    private final Map<String, Object> data;
    private AuthenticationToken token;

    /**
     * 构造服务上下文对象的实例。
     */
    public ServiceContext(
    ) {
        this.data = new TreeMap<>();
        this.token = null;
    }

    /**
     * 获取当前令牌。
     *
     * @return 当前令牌。<strong>此方法绝对不应当返回{@code null}。</strong>，如果当前令牌不存在则创建一个来宾令牌并返回。。
     * @throws IllegalStateException
     *         如果当前上下文尚未初始化。
     */
    public AuthenticationToken getToken() {
        assertInitialized();

        AuthenticationToken token = this.token;
        if (token == null) {
            this.token = token = AuthenticationToken.guest();
        }

        return token;
    }

    /**
     * 设置当前令牌。
     *
     * <p>如果参数{@code token}是{@code null}则表示清除当前令牌。</p>
     *
     * @param token
     *         当前令牌。
     * @throws IllegalStateException
     *         如果当前上下文尚未初始化。
     */
    public void setToken(
            AuthenticationToken token
    ) {
        assertInitialized();

        if (token == this.token) {
            // 避免重复赋值。
            return;
        }

        this.token = token;
    }

    /**
     * 获取包含所有键的集合。
     *
     * @return 包含所有键的集合。
     */
    public final Set<String> keySet() {
        return new TreeSet<>(this.data.keySet());
    }

    /**
     * 设置值，如果参数{@code key}是{@code null}则删除指定的键。
     *
     * @param key
     *         键。
     * @param value
     *         对应的属性值。
     * @return 获取之前关联的值。
     * @throws IllegalStateException
     *         如果当前上下文尚未初始化。
     * @throws IllegalArgumentException
     *         如果参数{@code key}是{@code null}或者如果参数{@code key}只包含空白字符。
     */
    public final Object put(
            String key,
            Object value
    ) {
        notBlank(key, "key");

        if (value != null) {
            return this.data.put(key, value);
        } else {
            return this.data.remove(key);
        }
    }

    /**
     * 获取值。
     *
     * @param key
     *         键。
     * @return {@code key}对应的值，如果不存在则返回{@code null}。
     * @throws IllegalStateException
     *         如果当前上下文尚未初始化。
     * @throws IllegalArgumentException
     *         如果参数{@code key}是{@code null}或者如果参数{@code key}只包含空白字符。
     */
    public final Object get(
            String key
    ) {
        notBlank(key, "key");

        return this.data.get(key);
    }

    /**
     * 获取值。
     *
     * @param key
     *         键。
     * @param supplier
     *         用于生成属性值的计算函数。
     * @return {@code key}对应的值，如果不存在则使用{@code supplier}计算，并将计算结果保存。
     * @throws IllegalStateException
     *         如果当前上下文尚未初始化。
     */
    public final Object computeIfAbsent(
            String key,
            Supplier<?> supplier
    ) {
        notBlank(key, "key");

        Object value = this.get(key);
        if (value == null) {
            value = supplier.get();

            this.put(key, value);
        }
        return value;
    }

    /**
     * 记录业务操作日志，标记执行一项操作。
     *
     * @param stage
     *         操作的阶段。
     * @param category
     *         业务操作的类别，自动去掉首尾空格。
     * @param action
     *         业务操作的动作，自动去掉首尾空格。
     * @param target
     *         业务操作的对象，自动去掉首尾空格。
     * @param detail
     *         业务操作的明细，自动去掉首尾空格。
     * @throws IllegalStateException
     *         如果当前上下文尚未初始化。
     * @throws IllegalArgumentException
     *         如果参数 {@code stage}是{@code null}或者 {@code category}或者{@code action}是{@code null}或者只包含空白字符。
     */
    public void log(
            TaskLogStage stage,
            String category,
            String action,
            String target,
            String detail
    ) {
        assertInitialized();

        notNull(stage, "stage");
        category = notBlank(category, "category").trim();
        action = notBlank(action, "action").trim();
        target = trimToEmpty(target);
        detail = trimToEmpty(detail);

        final BoundParamCollection params = (BoundParamCollection) get(PARAMS_KEY);
        if (params != null && params.size() > 0) {
            final StringBuilder stringBuilder = new StringBuilder("---- parameters ----\n");
            for (final BoundParam param : params) {
                final String name = param.getName();
                final Object value = param.getValue();
                if (name.startsWith("__")) {
                    // 跳过系统预定义参数。
                    continue;
                }
                if (value == null) {
                    continue;
                }

                stringBuilder.append(name).append("=");

                if (value instanceof BigDecimal) {
                    stringBuilder.append(((BigDecimal) value).toPlainString());
                } else if (value instanceof Date) {
                    stringBuilder.append(format((Date) value, "yyyy-MM-dd HH:mm:ss ZZZ"));
                } else if (value instanceof byte[]) {
                    stringBuilder.append("byte[").append(Array.getLength(value)).append("]");
                } else if (value.getClass().isArray()) {
                    stringBuilder.append(value.getClass().getComponentType()).append("[").append(Array.getLength(value)).append("]");
                } else if (value instanceof CharSequence) {
                    stringBuilder.append("\"").append(value).append("\"");
                } else {
                    stringBuilder.append(value.toString());
                }

                stringBuilder.append("\n");
            }
            stringBuilder.append("------------");
            detail = detail + LOG_DETAIL_SEPARATOR + stringBuilder.toString();
        }

        final Date now = new Date();

        logger.info(TASK_LOGGER_MARKER,
                    "{\"timestamp\":\"{}\",\"stage\":\"{}\",\"category\":\"{}\",\"action\":\"{}\",\"user\":\"{}\",\"target\":\"{}\",\"detail\":\"{}\"}",
                    formatUTC(now, "yyyy-MM-dd'T'HH:mm:ssZZ"),
                    stage,
                    escapeJson(category),
                    escapeJson(action),
                    escapeJson(getToken().getUserId()),
                    escapeJson(target),
                    escapeJson(detail));
    }
}
